package defectsim;

import java.io.IOException;

import repast.simphony.context.Context;
import repast.simphony.context.space.continuous.ContinuousSpaceFactoryFinder;
import repast.simphony.context.space.grid.GridFactoryFinder;
import repast.simphony.dataLoader.ContextBuilder;
import repast.simphony.engine.environment.RunEnvironment;
import repast.simphony.engine.schedule.ISchedule;
import repast.simphony.engine.schedule.ScheduleParameters;
import repast.simphony.random.RandomHelper;
import repast.simphony.space.grid.GridBuilderParameters;
import repast.simphony.valueLayer.GridValueLayer;
import defectsim.DefectFactory.DefectType;
import defectsim.MaterialFactory.MaterialName;

public class TerracottaContextCreator implements ContextBuilder<DefectAgent>{

	private SimEnvironment env = SimEnvironment.getInstance();

	// Initial configuration
	private int initialNumberOfDislocations = 1000;
	private int initialNumberOfDislocationSources = 60;
//	private int initialNumberOfObstacles = 0;


	@SuppressWarnings("unchecked")
	public Context build(Context context) {

		initializeSimulatedEnvironment(context);

		setContextVariables(context);

		initializeSimulation(context);

		ISchedule schedule = RunEnvironment.getInstance().getCurrentSchedule();
		ScheduleParameters params = ScheduleParameters.createRepeating(1, 1);
		schedule.schedule(params, this, "step");

		params = ScheduleParameters.createAtEnd(ScheduleParameters.LAST_PRIORITY);
		schedule.schedule(params, this, "endSimulation");

		return context;
	}

	@SuppressWarnings("unchecked")
	private void initializeSimulatedEnvironment(Context context) {
		env.setContext(context);
		env.setCurrentMaterial(MaterialName.Copper);
		env.setGridName("Simple Grid");
		env.setSpaceName("Continuous Space");
		env.setGridSize(800);
		env.setInitialTimeStep(1e-7);
		env.setSpaceLength(20);
		env.setMaxCutOffRadius(env.getSpaceLength() / 5.0);
		env.setMinCutOffRadius(env.getSpaceLength() / 10.0);
		env.setAnnihilationLength(10);
		env.setDipoleLength(3000);
		env.setElasticStressFieldName("Elastic Field");
		env.setCyclicTickCount(50);
		env.setSaveData(false);
		Stress stress = new Stress(0.003, 0.00036, Stress.ExternalStressType.Cyclic, env.getCyclicTickCount());
		env.setStress(stress);
	}

	@SuppressWarnings("unchecked")
	private void setContextVariables(Context context) {

		ContinuousSpaceFactoryFinder.createContinuousSpaceFactory(null)
		.createContinuousSpace(env.getSpaceName(), context, new DefectCartesianAdder<DefectAgent>(),
				new repast.simphony.space.continuous.WrapAroundBorders(), 
				env.getSpaceLength(), env.getSpaceLength(), 1);

		GridFactoryFinder.createGridFactory(null).createGrid(env.getGridName(), context,
				new GridBuilderParameters(new repast.simphony.space.grid.WrapAroundBorders(),
						new DefectGridAdder<DefectAgent>(), true, env.getGridSize(), env.getGridSize()));

		GridValueLayer eStressField = new GridValueLayer(env.getElasticStressFieldName(), true, 
				new repast.simphony.space.grid.WrapAroundBorders(), env.getGridSize(), env.getGridSize());
		context.addValueLayer(eStressField);

	}

	private void initializeSimulation(Context<DefectAgent> context){

		double gridScale = env.getGridSize() / env.getSpaceLength();
		double[] slipsys = env.getCurrentMaterial().getSlipSystems();
		Material material = env.getCurrentMaterial();
		int noDislocationForEachSystem = (int) initialNumberOfDislocations / 
		(2 * material.getNumberOfActiveSlipSystems());
		for (int s = 0; s < material.getNumberOfActiveSlipSystems(); s++){

			for (int i = 0; i < noDislocationForEachSystem; i++) {
				EdgeDislocation dislocation = (EdgeDislocation) DefectFactory.createDefect(DefectType.EdgeDislocation);
				dislocation.setDirection(slipsys[s]);
				dislocation.setUnitBurgersVector(1);
				dislocation.setX(findRandomLocation());
				dislocation.setY(findRandomLocation());
				dislocation.setLocationGridX((int)(dislocation.getX() * gridScale));
				dislocation.setLocationGridY((int)(dislocation.getY() * gridScale));
				context.add(dislocation);
			}
			for (int i = 0; i < noDislocationForEachSystem; i++) {
				EdgeDislocation dislocation = (EdgeDislocation) DefectFactory.createDefect(DefectType.EdgeDislocation);
				dislocation.setDirection(slipsys[s]);
				dislocation.setUnitBurgersVector(-1);
				dislocation.setX(findRandomLocation());
				dislocation.setY(findRandomLocation());
				dislocation.setLocationGridX((int)(dislocation.getX() * gridScale));
				dislocation.setLocationGridY((int)(dislocation.getY() * gridScale));
				context.add(dislocation);
			}
		}
		int noDislocationSourceForEachSystem = (int) initialNumberOfDislocationSources / 
		(2 * material.getNumberOfActiveSlipSystems());
		for (int s = 0; s < material.getNumberOfActiveSlipSystems(); s++){
			for (int i = 0; i < noDislocationSourceForEachSystem; i++) {
				DislocationSource source = (DislocationSource) DefectFactory.createDefect(DefectType.DislocationSource);
				source.setDirection(slipsys[s]);
				source.setX(findRandomLocation());
				source.setY(findRandomLocation());
				source.setLocationGridX((int)(source.getX() * gridScale));
				source.setLocationGridY((int)(source.getY() * gridScale));
				context.add(source);
			}
			for (int i = 0; i < noDislocationSourceForEachSystem; i++) {
				DislocationSource source = (DislocationSource) DefectFactory.createDefect(DefectType.DislocationSource);
				source.setDirection(slipsys[s]);
				source.setX(findRandomLocation());
				source.setY(findRandomLocation());
				source.setLocationGridX((int)(source.getX() * gridScale));
				source.setLocationGridY((int)(source.getY() * gridScale));
				context.add(source);
			}
		}
	}

	public void step(){
		env.update();
	}

	public void endSimulation() throws IOException {
		env.saveData();
		System.out.println("Simulation stopped!");
	}

	private double findRandomLocation() {
		return RandomHelper.nextDoubleFromTo(0,	env.getSpaceLength());
	}
}
